﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Acceron.University.DBAccess;

namespace GA_Scheduler
{
    public class Schedule
    {
        private List<DaySchedule> _genes;
        int gencount = 0;

        public List<DaySchedule> Genes { get { return _genes; } private set { _genes = value; } }

        public float Fitness { get; private set; }

        public Schedule()
        {
            this._genes = new List<DaySchedule>();
        }

        /// <summary>
        /// Crossovers two chromosomes to one
        /// </summary>
        /// <param name="parent">second chromosome</param>
        /// <returns>Crossoved chromosome</returns>
        public Schedule Crossover(Schedule parent)
        {
            if (rand() % 100 > GA_Scheduler.Properties.Settings.Default.CrossoverProbability)
            {
                //return this.Copy();
                return null;
            }

            var newSch = new Schedule();

            // number of classes
            //int size = Configuration.Instance.NumberOfClasses;
            int size = Genes.Count;

            bool[] cp = new bool[size];

            // determine crossover point (randomly)
            for (int i = GA_Scheduler.Properties.Settings.Default.NumberOfCrossoverPoints; i > 0; i--)
            {
                while (true)
                {
                    int p = rand() % size;
                    if (!cp[p])
                    {
                        cp[p] = true;
                        break;
                    }
                }
            }

            bool first = rand() % 2 == 0;
            for (int i = 0; i < this.Genes.Count; i++)
            {
                if (first)
                {
                    newSch.Genes.Add(this.Genes[i]);
                }
                else
                {
                    newSch.Genes.Add(parent.Genes[i]);
                }
                // crossover point
                if (cp[i])
                    // change source chromosome
                    first = !first;
            }
            
            newSch.CalculateFitness();

            return newSch;
        }

        internal void MutationPrototype()
        {
            var rooms = Configuration.Instance.Rooms;
           
            for (int i = 0; i < GA_Scheduler.Properties.Settings.Default.MutationSize; i++)
            {
                int mpos = rand() % this.Genes.Count;
                var gen = this.Genes[mpos];

                gen.DayOfWeek = rand() % GA_Scheduler.Properties.Settings.Default.CountDays;
                gen.Room = rooms[rand() % Configuration.Instance.NumberOfRooms];
                gen.Pair = rand() % (12 - gen.Class.Duration);

            }

                CalculateFitness();
        }

        /// <summary>
        /// Mutation chromosome with mutation probability
        /// </summary>
        public void Mutation()
        {
            var rooms = Configuration.Instance.Rooms;
            // check probability of mutation operation
            if (rand() % 1000 > GA_Scheduler.Properties.Settings.Default.MutationProbability)
                return;

            for (int i = 0; i < GA_Scheduler.Properties.Settings.Default.MutationSize; i++)
            {
                int mpos = rand() % this.Genes.Count;
                var gen = this.Genes[mpos];

                gen.DayOfWeek = rand() % GA_Scheduler.Properties.Settings.Default.CountDays;
                gen.Room = rooms[rand() % Configuration.Instance.NumberOfRooms];
                gen.Pair = rand() % (12 - gen.Class.Duration);
                
            }

            CalculateFitness();
        }

       
        /// <summary>
        /// Calculates fitness for chromosome. Than more than better.
        /// </summary>
        private void CalculateFitness()
        {
            int score = 0;
            
            if (gencount == 0)
                gencount = _genes.Count;

            List<DaySchedule> selectedGenes = new List<DaySchedule>();
            
            foreach (var gen in Genes)
            {
                selectedGenes.Clear();

                for (int i = 0; i < gencount; i++)
                {
                    var g = _genes[i];
                    if(g.DayOfWeek == gen.DayOfWeek && gen.Class_id != g.Class_id)
                        selectedGenes.Add(g);
                }


                var roomOv = selectedGenes.FirstOrDefault(g => (g.Pair >= gen.Pair && g.Pair <= gen.Pair + gen.Class.Duration 
                    || g.Pair < gen.Pair && g.Pair + g.Class.Duration >= gen.Pair) && g.Room_id == gen.Room_id);


                var profOv = selectedGenes.FirstOrDefault(g => (g.Pair >= gen.Pair && g.Pair <= gen.Pair + gen.Class.Duration
                    || g.Pair < gen.Pair && g.Pair + g.Class.Duration >= gen.Pair) && g.Class.Prof_ID == gen.Class.Prof_ID);


                var groupsOv = selectedGenes.Where(g => (g.Pair >= gen.Pair && g.Pair <= gen.Pair + gen.Class.Duration
                   || g.Pair < gen.Pair && g.Pair + g.Class.Duration >= gen.Pair));

                bool grOverlap = false;

                foreach (var gr in gen.Class.GroupClasses)
                {
                    var grOv = groupsOv.FirstOrDefault(g => g.Class.GroupClasses.FirstOrDefault(t => t.Groupd_ID == gr.Groupd_ID) != null);

                    if (grOv != null)
                    {
                        grOverlap = true;
                        break;
                    }
                }

                if (!grOverlap)
                    score++;
                

                if (roomOv == null)
                {
                    score++;
                }

                if (profOv == null)
                {
                    score++;
                }


                if (gen.Room.IsLab == true && gen.Class.Lab == true || (gen.Class.Lab == null || gen.Class.Lab == false))
                    score++;

                // Check size of room
                if (gen.Class.NumberOfSeats() < gen.Room.Size)
                    score++;

                //Fitness = (float)score / (Genes.Count * 5);
                continue;

                 //= Genes.Where(g => g.DayOfWeek == gen.DayOfWeek && g.Class_id != gen.Class_id).ToList();

                bool roomsoverl = false;
                bool overl = false;
                bool profsoveral = false;
                for (int i = 0; i < selectedGenes.Count; i++)
                {
                    if (selectedGenes[i].Pair == gen.Pair)
                    {
                        overl = true;
                    }

                    if (selectedGenes[i].Room_id == gen.Room_id)
                        roomsoverl = true;

                    if (selectedGenes[i].Class.Prof_ID == gen.Class.Prof_ID)
                        profsoveral = true;

                    if (roomsoverl && overl && profsoveral)
                        break;
                }
                /*
                var overl = (from g in selectedGenes
                            where g.Pair == gen.Pair
                            select g).Count();
                */
                if (!overl)
                    score++;

                // Check overlaps for rooms
                /*var rooms = (from g in selectedGenes
                            where g.Room_id == gen.Room_id &&
                            g.Pair == gen.Pair
                            select g).Count();
                    */
                if (!roomsoverl)
                    score++;

                

                // Check overlaps for professors
               /* var profs = (from p in selectedGenes
                             where p.Class.Prof_ID == gen.Class.Prof_ID &&
                             p.Pair == gen.Pair
                             select p).Count();
                */
                if (!profsoveral)
                    score++;

                // Check groups overlaps
                bool grOverlaps = false;
                foreach (var gr in gen.Class.GroupClasses)
                {
                    for (int i = 0; i < selectedGenes.Count; i++)
                    {
                        if (selectedGenes[i].Pair == gen.Pair && selectedGenes[i].Class.GroupClasses.Contains(gr))
                            grOverlaps = true;
                        break;
                    }

                    /*var groups = (from g in selectedGenes
                                  where g.Class.GroupClasses.Contains(gr) &&
                                  g.Pair == gen.Pair
                                  select g).Count();

                    if (groups > 0)
                    {
                        grOverlaps = true;
                        break;
                    }*/
                }

                if (!grOverlaps)
                    score++;
            }

            // In an ideal case fitness will be equals 1
            Fitness = (float)score / (Genes.Count * 5);
        }

        /// <summary>
        /// Gets a copy of this chromosome
        /// </summary>
        /// <returns>Schedule with the same parametres</returns>
        public Schedule Copy()
        {
            DaySchedule[] copiedarr = new DaySchedule[this.Genes.Count];
            this.Genes.CopyTo(copiedarr);
            return new Schedule()
            {
                Genes = copiedarr.ToList()
            };
        }

        private static Schedule _prototype;

        private static Schedule CreatePrototype()
        {
            Schedule newSch = new Schedule();
            var rooms = Configuration.Instance.Rooms;
            var classes = Configuration.Instance.Classes;


            foreach (var gr in Configuration.Instance.Groups)
            {
                int courseIndex = 0;
                for (int i = 0; i < GA_Scheduler.Properties.Settings.Default.CountDays; i++)
                {
                    int pair = 0;
                    //foreach (var gc in gr.GroupClasses)
                    for (int j = courseIndex; j < gr.GroupClasses.Count; j++)
                    {
                        var exist = newSch._genes.FirstOrDefault(g => g.Class_id == gr.GroupClasses[j].Class_ID);
                        if (exist != null)
                            continue;

                        if (pair + gr.GroupClasses[j].Class.Duration > 12)
                            continue;
                        
                        var grOv = newSch._genes.FirstOrDefault(g => g.DayOfWeek == i && (g.Pair >= j && g.Pair <= j + gr.GroupClasses[j].Class.Duration
                   || g.Pair < j && g.Pair + g.Class.Duration >= j) && g.Class.GroupClasses.FirstOrDefault(t => t.Groupd_ID == gr.Group_ID) != null);

                        if (grOv != null)
                        {
                            j--;
                            pair++;
                            continue;
                        }
                        courseIndex = j;

                        var ds = new DaySchedule()
                        {
                            Class = gr.GroupClasses[j].Class,
                            DayOfWeek = i,
                            Pair = pair,
                            Room = rooms[rand() % Configuration.Instance.NumberOfRooms]
                        };
                        pair += gr.GroupClasses[j].Class.Duration;
                        newSch.Genes.Add(ds);
                    }
                }
            }

            /*
            foreach (var cl in classes)
            {
                var dw = rand() % GA_Scheduler.Properties.Settings.Default.CountDays;
                var pair =  rand() % (12 - cl.Duration);
                var room = rooms[rand() % Configuration.Instance.NumberOfRooms];

                //for (int i = 0; i < cl.Duration; i++)
                {
                    var ds = new DaySchedule()
                    {
                        Class = cl,
                        DayOfWeek = dw,
                        Pair = pair,
                        Room = room
                    };
                    newSch.Genes.Add(ds);
                }
            }*/

            return newSch;
        }

        /// <summary>
        /// Generates new chromosome with random parametres
        /// </summary>
        /// <returns>Generated schedule with random parametres</returns>
        public static Schedule GetFromPrototype()
        {
            if (_prototype == null)
            {
                _prototype = CreatePrototype();
                _prototype.CalculateFitness();
                return _prototype.Copy();
            }

            _prototype.Mutation();
            
            return _prototype.Copy();
        }

        /// <summary>
        /// C++ like helper-function
        /// </summary>
        /// <returns>Int32 pseudo-randomly generated number</returns>
        private static int rand()
        {
            return Configuration.Instance.Rand.Next();
        }
    }
}
